home *** CD-ROM | disk | FTP | other *** search
/ EnigmA Amiga Run 1996 June / EnigmA AMIGA RUN 08 (1996)(G.R. Edizioni)(IT)[!][issue 1996-06][EARSAN CD VII].iso / earcd / utilsys / updatcpy.lha / UpdateCopy / Source / updatecopy.e
Text File  |  1996-04-15  |  20KB  |  639 lines

  1. /* Updatecopy.e
  2. ** Copies multiple files/directories to one destination.
  3. ** Not existing directories are created.
  4. ** Already existing files are only replaced by newer ones
  5. ** (updatecopy first checks for versions-string and then
  6. **  for dates).
  7. **
  8. ** $VER: UpdateCopy.e 0.40 (14.04.96)
  9. **
  10. ** This program is Cardware. If you use it you should send an Email to
  11. ** the author. Also small presents are very welcome.
  12. ** You may use this sourcefile or parts of it freely in your programs.
  13. ** But please do not spread a modified version under this name (UpdateCopy).
  14. ** For Bugreports, ideas or anything else send a Email to:
  15. **    ss37@irz.inf.tu-dresden.de
  16. ** 
  17. ** Sven Steiniger, 1996
  18. **
  19. ** Fold-start: ->// ""
  20. ** Fold-stop:  ->\\
  21. */
  22.  
  23. OPT OSVERSION=37
  24.  
  25. MODULE 'dos/dos','dos/dosasl','dos/dostags',
  26.        'tools/file'
  27.  
  28. RAISE "MEM"  IF String()=NIL,
  29.       "ADDP" IF AddPart()=DOSFALSE,
  30.       "CTRL" IF CtrlC()=TRUE,
  31.       "COPY" IF FileLength()=-1,
  32.       "MEM"  IF AllocDosObject()=NIL
  33.  
  34. CONST MAXPATH=255,                -> Maximum path length
  35.       SPACEADD=3,                 -> Number of spaces per indent
  36.       SAFETYBYTES=100,            -> Maximum length of versionstring
  37.       BIGFILESIZE=100000          -> Files bigger than that are not read
  38.                                   -> completly into memory
  39. CONST PATHLENGTH=MAXPATH-1,
  40.       BIGFILEMEM=BIGFILESIZE+SAFETYBYTES
  41.  
  42. DEF frompath[MAXPATH]:STRING,     -> actual sourcepath
  43.     fromlist:PTR TO LONG,         -> pointer to array of sourcestrings
  44.     topath[MAXPATH]:STRING,       -> destinationpath
  45.     doinfo,                       -> should we write informations ?
  46.     recursiv,                     -> scan recursively through subdirectories ?
  47.     ignoreprotection,             -> clear delete-protection ?
  48.     checkversion,                 -> compare version-strings ?
  49.     executestri[MAXPATH]:STRING,  -> used with SystemTagList()
  50.     dirlock=NIL                   -> ptr to lock-strcuture of destination path
  51.  
  52. PROC main() HANDLE
  53. ->// "main()"
  54. DEF rdargs=NIL,
  55.     myargs:PTR TO LONG,
  56.     template
  57.  
  58.   /* Initialize argument-array */
  59.   myargs:=[NIL,
  60.            NIL,
  61.            FALSE,
  62.            FALSE,
  63.            FALSE,
  64.            FALSE]:LONG
  65.  
  66.   /* Parse Commandline */
  67.   IF rdargs:=ReadArgs(template:='FROM/M,TO/A,QUIET/S,ALL/S,FORCE/S,DEEP/S',myargs,NIL)
  68.  
  69.     /* Copy Datas to global variables */
  70.     fromlist         := myargs[0]
  71.     StrCopy(topath,myargs[1],ALL)
  72.     doinfo           := Not(myargs[2])
  73.     recursiv         := myargs[3]
  74.     ignoreprotection := myargs[4]
  75.     checkversion     := myargs[5]
  76.  
  77.     IF fromlist=NIL THEN Throw("ARGS",'No source specified.')
  78.     init_arguments()
  79.  
  80.     IF doinfo THEN PrintF('Updating files to "\s".\n',topath)
  81.  
  82.     WHILE fromlist[]                     -> 'fromlist' is array of string-pointers
  83.       StrCopy(frompath,fromlist[]++,ALL) -> copy the string and increment 'fromlist'
  84.       scan_directory(frompath,'',1)      -> then process this directory
  85.     ENDWHILE
  86.  
  87.   ELSE -> there were errors in commandline, show some info
  88.  
  89.     PrintF('  UpdateCopy v0.40\n'+
  90.            ' -----------------\n'+
  91.            '  Copy files and directories.\n'+
  92.            '  Not existing directories are created and\n'+
  93.            '  already existing files are only replaced\n'+
  94.            '  by newer ones. Allows pattern-matching and\n'+
  95.            '  multiple sourcedirectories/files.\n\n'+
  96.            'Sven Steiniger, 1996\n\n')
  97.     PrintF('Template: \s\n',template)
  98.     PrintF('  FROM  - source directory/file(s)\n'+
  99.            '  TO    - destination directory\n'+           
  100.            '  QUIET - no outputs\n'+
  101.            '  ALL   - scans through subdirectories recursively\n'+
  102.            '  FORCE - ignore protectionbits\n'+
  103.            '  DEEP  - compare version-strings\n')
  104.   ENDIF
  105.  
  106. EXCEPT DO  -> Cleanup
  107.  
  108.   free_arguments()
  109.   IF rdargs THEN FreeArgs(rdargs)
  110.  
  111.   IF exception
  112.     /* Print error description */
  113.     PrintF('Error: ')
  114.     SELECT exception
  115.       CASE "MEM"  ; PrintF('Not enough memory.\n')
  116.       CASE "ADDP" ; PrintF('Path too long !?\n')
  117.       CASE "CTRL" ; PrintF('User abort.\n')
  118.       CASE "COPY" ; PrintF('Could not copy file.\n')
  119.       CASE "OPEN" ; PrintF('Could not open file.\n')
  120.       CASE "ANLY" ; PrintF('Could not analyse file.\n')
  121.       DEFAULT     ; PrintF('\s\n',exceptioninfo)
  122.     ENDSELECT
  123.   ENDIF
  124.  
  125.   CleanUp(exception)
  126.  
  127. ENDPROC
  128. ->\\
  129.  
  130. versionstring: CHAR '$VER: UpdateCopy 0.40 (14.04.96)',0
  131.  
  132.  
  133. /* checks the arguments provied by user
  134. */
  135. PROC init_arguments()
  136. ->// "init_arguments()"
  137.  
  138.   IF (dirlock:=Lock(topath,SHARED_LOCK))=NIL            -> check if destination
  139.     Throw("DIR",'Could not lock destination directory') -> directory exists
  140.   ENDIF
  141.  
  142. ENDPROC
  143. ->\\
  144.  
  145.  
  146. /* Cleanup datas allocated during init_arguments
  147. */
  148. PROC free_arguments()
  149. ->// "free_arguments()"
  150.   UnLock(dirlock)             -> Unlock destination directory
  151. ENDPROC
  152. ->\\
  153.  
  154.  
  155. /* Scans a directory and copies files/creates subdirectories
  156. ** Parameter:
  157. **   directory    - name of directory to be scanned
  158. **   path         - the current delta path
  159. **   depth        - recursion level
  160. ** Example:
  161. **   The source path is "esource:src".
  162. **   'directory' is "esource:src/tools/file".
  163. **   Then 'path' have to be "tools/file".
  164. */
  165. PROC scan_directory(directory,path,depth) HANDLE
  166. ->// "scan_directory()"
  167. DEF info:PTR TO fileinfoblock,
  168.     anchor=NIL:PTR TO anchorpath,
  169.     error,
  170.     fullpath,
  171.     mypath[MAXPATH]:STRING,
  172.     length
  173.  
  174.   /* Create and initialize anchor structure needed for
  175.   ** scanning through directory.
  176.   ** This structure has no fixed size.
  177.   */
  178.   anchor:=NewR(SIZEOF anchorpath+MAXPATH)
  179.   anchor.strlen:=PATHLENGTH
  180.  
  181.   -> Get start of string
  182.   fullpath:=anchor+SIZEOF anchorpath
  183.  
  184.   error:=MatchFirst(directory,anchor)
  185.   WHILE error=DOSFALSE
  186.  
  187.     CtrlC()
  188.  
  189.     info:=anchor.info                           -> get fileinfoblock
  190.     IF info.direntrytype>0                      -> is it a directory ?
  191.  
  192.       StrCopy(mypath,path,ALL)                  -> init new delta path
  193.       AddPart(mypath,info.filename,PATHLENGTH)  -> including new subdirectory
  194.  
  195.       printspaces(depth*SPACEADD)               -> looks better
  196.       process_directory(fullpath,info,mypath)   -> knows what to do with this
  197.                                                 -> directory
  198.  
  199.       IF recursiv                               -> scan subdirectories ?
  200.         length:=StrLen(fullpath)
  201.         IF (length+5)<MAXPATH
  202.           CopyMem('/#?',fullpath+length,4)      -> add pattern matching
  203.         ENDIF
  204.         scan_directory(fullpath,mypath,depth+1) -> call our self with new subdirectory
  205.         fullpath[length]:=0                     -> remove pattern matching
  206.       ENDIF
  207.  
  208.     ELSE
  209.  
  210.       printspaces(depth*SPACEADD)               -> indent line
  211.       process_file(fullpath,info,path)          -> knows what to do with it
  212.  
  213.     ENDIF
  214.  
  215.     error:=MatchNext(anchor)                    -> Next entry
  216.   ENDWHILE
  217.  
  218. EXCEPT DO
  219.  
  220.   IF anchor
  221.     MatchEnd(anchor)                            -> Clean up
  222.     Dispose(anchor)
  223.   ENDIF
  224.  
  225.   ReThrow()
  226.  
  227. ENDPROC
  228. ->\\
  229.  
  230.  
  231. /* Process an directory. If it doesnt exists it will be created
  232. ** Parameter:
  233. **  directory - full path of source directory
  234. **  info      - pointer to fileinfoblock of source directory
  235. **  path      - delta path of directory
  236. */
  237. PROC process_directory(directory,info:PTR TO fileinfoblock,path)
  238. ->// "process_directory()"
  239. DEF stri[MAXPATH]:STRING,
  240.     lock
  241.  
  242.   StrCopy(stri,topath,ALL)
  243.   AddPart(stri,path,PATHLENGTH)                  -> thats the destion-directory
  244.  
  245.   IF doinfo THEN PrintF('\e[1m\s\e[0m..',path)   -> Write directory name
  246.   IF lock:=CreateDir(stri)                       -> Create the directory
  247.     IF doinfo THEN PrintF('created.\n')
  248.     UnLock(lock)                                 ->  No erros, unlock directory
  249.   ELSE                                           -> Directory could not be created
  250.     IF IoErr()=ERROR_OBJECT_EXISTS               -> Either it already exists
  251.       IF doinfo THEN PrintF('skipped.\n')
  252.     ELSE
  253.       IF doinfo THEN PrintF('\n')
  254.       Throw("DIR",'Could not create directory')  -> or so something went wrong
  255.     ENDIF
  256.   ENDIF
  257.  
  258. ENDPROC
  259. ->\\
  260.  
  261.  
  262. /* Processes an file. If it does not exists or is newer we copy it.
  263. ** Parameter:
  264. **   file  - full source filename (include path)
  265. **   info  - ptr to fileinfoblock of sourcefile
  266. **   path  - deltapath to directory (exclude filename !)
  267. */
  268. PROC process_file(file,info:PTR TO fileinfoblock,path) HANDLE
  269. ->// "process_file()"
  270. DEF stri[MAXPATH]:STRING,
  271.     filepath[MAXPATH]:STRING,
  272.     fh=NIL,
  273.     toinfo=NIL:PTR TO fileinfoblock,
  274.     result=0,
  275.     frombuf=NIL
  276.  
  277.   StrCopy(filepath,path,ALL)                             -> create deltapath
  278.   AddPart(filepath,info.filename,PATHLENGTH)             -> inclusive filename
  279.  
  280.   StrCopy(stri,topath,ALL)                               -> create full destination
  281.   AddPart(stri,filepath,PATHLENGTH)                      -> filepath
  282.  
  283.   IF doinfo THEN PrintF('\s..',filepath)                 -> Write filename
  284.   IF fh:=Open(stri,MODE_OLDFILE)                         -> Open destinationfile
  285.  
  286.     /* Fileinfoblock have to be LONGWORD-aligned therefore
  287.     ** use dos.library to create this
  288.     */
  289.     toinfo:=AllocDosObject(DOS_FIB,NIL)
  290.     IF ExamineFH(fh,toinfo)<>DOSFALSE                    -> fill fileinfoblock
  291.  
  292.       IF checkversion THEN result,frombuf:=compareversion(file,stri)
  293.  
  294.       IF result=0
  295.         /* Compare versionstrings was either not specified by user
  296.         ** or was not successfull. Therefore Compare filedates
  297.         */
  298.         result:=CompareDates(toinfo.datestamp,info.datestamp)
  299.       ENDIF
  300.       IF result>0                                        -> fromfile newer than tofile ?
  301.         Close(fh) ; fh:=NIL                              -> close destination
  302.  
  303.         IF ignoreprotection THEN SetProtection(stri,0)   -> Clear protectionflags
  304.                                                          -> if specified
  305.         copyfile(file,stri,frombuf)                      -> copy the file
  306.         IF doinfo THEN PrintF('replaced.\n')
  307.       ELSE
  308.         IF doinfo THEN PrintF('skipped.\n')
  309.       ENDIF
  310.       /* compareversion() may returns us the contents of the
  311.       ** sourcefile. We must free this buffer
  312.       */
  313.       IF frombuf THEN Dispose(frombuf)
  314.  
  315.     ELSE
  316.       Throw("FILE",'Could not examine file ?!')
  317.     ENDIF
  318.   ELSE                                                   -> destination does not exists
  319.     copyfile(file,stri)                                  -> copy file
  320.     IF doinfo THEN PrintF('copied.\n')
  321.   ENDIF
  322.  
  323. EXCEPT DO  -> Cleanup
  324.  
  325.   IF toinfo THEN FreeDosObject(DOS_FIB,toinfo)
  326.   IF fh THEN Close(fh)
  327.   ReThrow()
  328.  
  329. ENDPROC
  330. ->\\
  331.  
  332.  
  333. /* Prints 'spaceanz' spaces
  334. */
  335. PROC printspaces(spaceanz)
  336. ->// "printspaces()"
  337.   IF doinfo
  338.     WHILE spaceanz-- >=0 DO PrintF(' ')
  339.   ENDIF
  340. ENDPROC
  341. ->\\
  342.  
  343.  
  344. /* Copies a file. For files >BIGFILESIZE Bytes c:copy is
  345. ** used.
  346. ** Parameter:
  347. **  fromfile  - full path of sourcefile
  348. **  tofile    - full path of destinationfile
  349. **  frombuf   - Contents of sourcefile. If NIL then sourcefile
  350. **              is read
  351. */
  352. PROC copyfile(fromfile,tofile,frombuf=NIL) HANDLE
  353. ->// "copyfile()"
  354. DEF fhfrom=NIL,
  355.     fhto=NIL,
  356.     length,
  357.     buf=NIL,
  358.     info=NIL:PTR TO fileinfoblock
  359.  
  360.   IF (length:=FileLength(fromfile))>BIGFILESIZE
  361.     /* Because unbuffered FileIO is used and the whole file is copied
  362.     ** to memory 'c:copy' should be more optimal for large files.
  363.     */
  364.     StringF(executestri,'c:Copy "\s" "\s" QUIET COM NOREQ',fromfile,tofile)
  365.     IF SystemTagList(executestri,NIL)<>DOSFALSE THEN Raise("COPY")
  366.   ELSE
  367.  
  368.     /* Fileinfoblock have to be LONGWORD-aligned therefore
  369.     ** use dos.library to create this
  370.     */
  371.     info:=AllocDosObject(DOS_FIB,NIL)
  372.     IF (fhfrom:=Open(fromfile,OLDFILE)) AND    -> open sourcefile
  373.        (fhto:=Open(tofile,NEWFILE)) AND        -> open destinationfile
  374.        (ExamineFH(fhfrom,info)<>DOSFALSE)      -> get sourcefile-data
  375.  
  376.       IF frombuf                               -> we already know the
  377.                                                -> contents of the sourcefile
  378.         /* Write buffer to destinationfile */
  379.         IF Write(fhto,frombuf,length)<>length THEN Raise("COPY")
  380.       ELSE
  381.         buf:=NewR(length)                      -> alloc new buffer
  382.  
  383.         /* Read sourcefile into buffer */
  384.         IF Read(fhfrom,buf,length)<>length THEN Raise("COPY")
  385.         /* Write buffer TO destinationfile */
  386.         IF Write(fhto,buf,length)<>length THEN Raise("COPY")
  387.       ENDIF
  388.       SetProtection(tofile,info.protection)    -> Copy protection bits
  389.       SetComment(tofile,info.comment)          -> Copy comment
  390.  
  391.     ELSE                                       -> There went something wrong
  392.       Raise("COPY")
  393.     ENDIF
  394.   ENDIF
  395.  
  396. EXCEPT DO -> Cleanup
  397.  
  398.   IF info THEN FreeDosObject(DOS_FIB,info)
  399.   IF buf THEN Dispose(buf)
  400.   IF fhfrom THEN Close(fhfrom)
  401.   IF fhto THEN Close(fhto)
  402.  
  403.   ReThrow()
  404.  
  405. ENDPROC
  406. ->\\
  407.  
  408.  
  409. /* Compares the version-strings of two files
  410. ** Parameter:
  411. **   fromfile - full path of sourcefile
  412. **   tofile   - full path of destinationfile
  413. **
  414. ** Returns
  415. **   -1 if tofile-version>fromfile-version
  416. **    0 if               =
  417. **    1 if               <
  418. ** *AND* the contents of the sourcefile or NIL
  419. */
  420. PROC compareversion(fromfile,tofile) HANDLE
  421. ->// "compareversion()"
  422. DEF buffer=NIL:PTR TO CHAR,
  423.     version1,base1,
  424.     version2,base2
  425.  
  426.   version2,base2,buffer:=getVersionOfFile(tofile)
  427.   /* We dont need the destination contents. Therefore delete buffer
  428.   */
  429.   IF buffer
  430.     Dispose(buffer)
  431.     buffer:=NIL
  432.   ENDIF
  433.   /* if version is -1 then no version-string was found */
  434.   IF version2=-1 THEN RETURN 0,NIL
  435.  
  436.   version1,base1,buffer:=getVersionOfFile(fromfile)
  437.   IF version1=-1 THEN RETURN 0,buffer
  438.  
  439.   /* Compare the version, reversion of source/destionation
  440.   ** multiply with other base to get same number of signifant numbers
  441.   */
  442.   IF (version2*base1)>=(version1*base2)
  443.     RETURN -1,buffer
  444.   ELSE
  445.     RETURN 1,buffer
  446.   ENDIF
  447.  
  448. EXCEPT -> Cleanup
  449.  
  450.   IF buffer THEN Dispose(buffer)
  451.   ReThrow()
  452.  
  453. ENDPROC
  454. ->\\
  455.  
  456.  
  457. /* Gets the version-number of a file
  458. ** Parameter:
  459. **   filename - full path of file
  460. ** Returns the version, base and a buffer with the contents of the file
  461. ** (description for version,base see getversion())
  462. ** buffer may be NIL if file was to large
  463. */
  464. PROC getVersionOfFile(filename) HANDLE
  465. ->// "getVersionOfFile()"
  466. DEF fh=NIL,
  467.     buffer=NIL:PTR TO CHAR,
  468.     version,base,
  469.     steplength,filelength,actlength
  470.  
  471.   IF fh:=Open(filename,OLDFILE)
  472.  
  473.     filelength:=FileLength(filename)
  474.     IF filelength>BIGFILESIZE
  475.       /* Files >BIGFILESIZE are not read completly into memory but in parts
  476.       ** of BIGFILESIZE Bytes.
  477.       ** As we may skip a versionstring the last SAFETYBYTES are copied
  478.       ** to start of new BIGFILESIZE block everytime.
  479.       */
  480.  
  481.       /* Alloc new buffer and read filecontents into it */
  482.       buffer:=NewR(BIGFILEMEM)
  483.       IF Read(fh,buffer,BIGFILEMEM)<>BIGFILEMEM THEN Raise("ANLY")
  484.  
  485.       /* steplength is he length of the current block to be read */
  486.       steplength:=BIGFILESIZE
  487.       /* actlength is the position within the file */
  488.       actlength:=BIGFILEMEM
  489.  
  490.       REPEAT
  491.  
  492.         /* Get version. If version=-1 then read next block */
  493.         version,base:=getversion(buffer,steplength+SAFETYBYTES)
  494.         IF version=-1
  495.  
  496.           /* The version-string was maybe at the end of buffer
  497.           ** and therfore skipped. Copy the last SAFETYBYTES bytes
  498.           ** to start of buffer to not lose the version-string.
  499.           */
  500.           CopyMem(buffer+steplength,buffer,SAFETYBYTES)
  501.  
  502.           /* Increase actlength. If actlength is greater than filelength
  503.           ** then set steplength to the number of bytes left.
  504.           */
  505.           actlength:=actlength+BIGFILESIZE
  506.           IF actlength>filelength
  507.             steplength:=filelength-(actlength-BIGFILESIZE)
  508.           ENDIF
  509.  
  510.           /* Read next block to buffer. If steplength<0 then EOF is reached */
  511.           IF steplength>0
  512.             IF Read(fh,buffer+SAFETYBYTES,steplength)<>steplength THEN Raise("ANLY")
  513.           ENDIF
  514.  
  515.         ENDIF
  516.  
  517.         /* Read until version-string was found or EOF */
  518.       UNTIL (version<>-1) OR (steplength<=0)
  519.  
  520.       Dispose(buffer)
  521.       buffer:=NIL
  522.  
  523.     ELSE
  524.       /* We have got a small file. Load it completly into memory.
  525.       ** Alloc new buffer and read filecontents into it
  526.       */
  527.       buffer:=NewR(filelength)
  528.       IF Read(fh,buffer,filelength)<>filelength THEN Raise("ANLY")
  529.  
  530.       /* get versions and reversion of sourcefile */
  531.       version,base:=getversion(buffer,filelength)
  532.     ENDIF
  533.  
  534.     /* We dont need the filehandle any longer */
  535.     Close(fh)
  536.     fh:=NIL
  537.  
  538.   ELSE
  539.     Raise("OPEN")
  540.   ENDIF
  541.  
  542. EXCEPT
  543.   IF fh THEN Close(fh)
  544.   IF buffer THEN Dispose(buffer)
  545.   ReThrow()
  546.  
  547. ENDPROC version,base,buffer
  548. ->\\
  549.  
  550.  
  551. /* Search for a version-string in a file
  552. ** Parameter:
  553. **   buffer       - Contents of file
  554. **   bufferlength - length of buffer in bytes
  555. ** Retuns version,base
  556. ** if no version-string was found then -1 is returned as version
  557. ** Example: version=81259, base=10000 means
  558. **          Versionnumber is 81259/10000=8.1259
  559. */
  560. PROC getversion(buffer:PTR TO CHAR,bufferlength)
  561. ->// "getversion()"
  562. DEF version=-1:REG,base:REG
  563.  
  564.       MOVEA.L buffer,A0           -> A0..buffer
  565.       MOVE.L  bufferlength,D0     -> D0..bufferlength
  566.       SUBQ.L  #1,D0
  567.       MOVE.B  #"$",D1
  568.       MOVE.L  #"VER:",D2
  569. gv_search_loop:
  570.       SUBQ.L  #1,D0               
  571.       BLT.W   gv_ende             -> bufferend reached ?
  572.       CMP.B   (A0)+,D1
  573.       BNE.S   gv_search_loop      -> Found a "$" ?
  574.       CMP.L   (A0),D2             -> Yes, then next characters="VER:"
  575.       BNE.S   gv_search_loop
  576.  
  577.       SUBQ.L  #4,D0               -> We have found a version-string
  578.       BLT.S   gv_ende
  579.       ADDQ.L  #4,A0               -> skip "VER:"
  580.  
  581.       MOVE.B  #" ",D1
  582. gv_skipspaces1:                   -> skip all spaces before programname
  583.       SUBQ.L  #1,D0
  584.       BLT.S   gv_ende             -> bufferend reached ?
  585.       CMP.B   (A0)+,D1
  586.       BEQ.S   gv_skipspaces1
  587.       ADDQ.L  #1,D0               -> all spaces skipped; we have gone
  588.       SUBQ.L  #1,A0               -> one step too far.
  589.  
  590. gv_skipname:                      -> skip programname
  591.       SUBQ.L  #1,D0
  592.       BLT.S   gv_ende             -> bufferend reached ?
  593.       CMP.B   (A0)+,D1
  594.       BNE.S   gv_skipname
  595.       ADDQ.L  #1,D0               -> go one step back
  596.       SUBQ.L  #1,A0
  597.  
  598. gv_skipspaces2:                   -> skip spaces before version-number
  599.       SUBQ.L  #1,D0
  600.       BLT.S   gv_ende             -> bufferend reached ?
  601.       CMP.B   (A0)+,D1
  602.       BEQ.S   gv_skipspaces2
  603.       ADDQ.L  #1,D0               -> go one step back
  604.       SUBQ.L  #1,A0
  605.  
  606.       MOVEQ   #0,version
  607.       MOVE.B  #".",D1
  608.       MOVEQ   #0,D2
  609. gv_getversion1:                   -> get version until we found a "."
  610.       SUBQ.L  #1,D0
  611.       BLT.S   gv_ende             -> bufferend reached ?
  612.       MOVE.B  (A0)+,D2
  613.       SUBI.L  #"0",D2             -> Transform character "0".."9" into number
  614.       MULU.W  #10,version         -> version:=version*10
  615.       ADD.L   D2,version          ->                    +number
  616.       CMP.B   (A0),D1
  617.       BNE.S   gv_getversion1
  618.       SUBQ.L  #1,D0               -> skip "."
  619.       ADDQ.L  #1,A0
  620.  
  621.       MOVE.B  #" ",D1
  622.       MOVEQ   #1,base
  623. gv_getversion2:                   -> get reversion
  624.       SUBQ.L  #1,D0
  625.       BLT.S   gv_ende             -> bufferend reached ?
  626.       MOVE.B  (A0)+,D2
  627.       SUBI.L  #"0",D2             -> Transform character "0".."9" into number
  628.       MULU.W  #10,version         -> version:=version*10
  629.       MULU.W  #10,base            -> base:=base*10
  630.       ADD.L   D2,version          ->                    +number
  631.       CMP.B   (A0),D1
  632.       BNE.S   gv_getversion2
  633.  
  634. gv_ende:
  635.  
  636. ENDPROC version,base
  637. ->\\
  638.  
  639.